var Sylvester = {
    version: '0.1.3',
    precision: 1e-6
};

function Vector(){
}

Vector.prototype = {

    // Returns element i of the vector
    e: function(i){
        return (i < 1 || i > this.elements.length) ? null : this.elements[i - 1];
    },
    
    // Returns the number of elements the vector has
    dimensions: function(){
        return this.elements.length;
    },
	
	dim: function(){
		return this.dimensions();
	},
    
    // Returns the modulus ('length') of the vector
    modulus: function(){
        return Math.sqrt(this.dot(this));
    },
    
    length: function(){
        return this.modulus();
    },
    
    // Returns true iff the vector is equal to the argument
    eql: function(vector){
        var n = this.elements.length;
        var V = vector.elements || vector;
        if (n != V.length) {
            return false;
        }
        do {
            if (Math.abs(this.elements[n - 1] - V[n - 1]) > Sylvester.precision) {
                return false;
            }
        }
        while (--n);
        return true;
    },
    
    // Returns a copy of the vector
    dup: function(){
        return Vector.create(this.elements);
    },
    
    // Maps the vector to another vector according to the given function
    map: function(fn){
        var elements = [];
        this.each(function(x, i){
            elements.push(fn(x, i));
        });
        return Vector.create(elements);
    },
    
    // Calls the iterator for each element of the vector in turn
    each: function(fn){
        var n = this.elements.length, k = n, i;
        do {
            i = k - n;
            fn(this.elements[i], i + 1);
        }
        while (--n);
    },
    
    // Returns a new vector created by normalizing the receiver
    toUnitVector: function(){
        var r = this.modulus();
        if (r === 0) {
            return this.dup();
        }
        return this.map(function(x){
            return x / r;
        });
    },
	
	normalize: function() {
		return this.toUnitVector();	
	},
            
    // Returns the result of adding the argument to the vector
    add: function(vector){
        var V = vector.elements || vector;
        if (this.elements.length != V.length) {
            return null;
        }
        return this.map(function(x, i){
            return x + V[i - 1];
        });
    },
    
    // Returns the result of subtracting the argument from the vector
    subtract: function(vector){
        var V = vector.elements || vector;
        if (this.elements.length != V.length) {
            return null;
        }
        return this.map(function(x, i){
            return x - V[i - 1];
        });
    },
    
    // Returns the result of multiplying the elements of the vector by the argument
    multiply: function(k){
        return this.map(function(x){
            return x * k;
        });
    },
    
    x: function(k){
        return this.multiply(k);
    },
    
    // Returns the scalar product of the vector with the argument
    // Both vectors must have equal dimensionality
    dot: function(vector){
        var V = vector.elements || vector;
        var i, product = 0, n = this.elements.length;
        if (n != V.length) {
            return null;
        }
        do {
            product += this.elements[n - 1] * V[n - 1];
        }
        while (--n);
        return product;
    },
    
    // Returns the vector product of the vector with the argument
    // Both vectors must have dimensionality 3
    cross: function(vector){
        var B = vector.elements || vector;
        if (this.elements.length != 3 || B.length != 3) {
            return null;
        }
        var A = this.elements;
        return Vector.create([(A[1] * B[2]) - (A[2] * B[1]), (A[2] * B[0]) - (A[0] * B[2]), (A[0] * B[1]) - (A[1] * B[0])]);
    },
    
    // Returns the (absolute) largest element of the vector
    max: function(){
        var m = 0, n = this.elements.length, k = n, i;
        do {
            i = k - n;
            if (Math.abs(this.elements[i]) > Math.abs(m)) {
                m = this.elements[i];
            }
        }
        while (--n);
        return m;
    },
        
	
    
    // Returns a string representation of the vector
    inspect: function(){
        return '[' + this.elements.join(', ') + ']';
    },
    
    // Set vector's elements from an array
    setElements: function(els){
        this.elements = (els.elements || els).slice();
        return this;
    }
};

// Constructor function
Vector.create = function(elements){
    var V = new Vector();
    return V.setElements(elements);
};

// i, j, k unit vectors
Vector.i = Vector.create([1, 0, 0]);
Vector.j = Vector.create([0, 1, 0]);
Vector.k = Vector.create([0, 0, 1]);

// Random vector of size n
Vector.Random = function(n){
    var elements = [];
    do {
        elements.push(Math.random());
    }
    while (--n);
    return Vector.create(elements);
};

// Vector filled with zeros
Vector.Zero = function(n){
    var elements = [];
    do {
        elements.push(0);
    }
    while (--n);
    return Vector.create(elements);
};

$V = Vector.create;

